/*
  spindle_control.h - spindle control methods

  Part of grblHAL

  Copyright (c) 2017-2024 Terje Io
  Copyright (c) 2012-2015 Sungeun K. Jeon
  Copyright (c) 2009-2011 Simen Svale Skogsrud

  grblHAL is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  grblHAL is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with grblHAL. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef _SPINDLE_CONTROL_H_
#define _SPINDLE_CONTROL_H_

#include "pid.h"

#define SPINDLE_NONE        0
#define SPINDLE_HUANYANG1   1
#define SPINDLE_HUANYANG2   2
#define SPINDLE_GS20        3
#define SPINDLE_YL620A      4
#define SPINDLE_MODVFD      5
#define SPINDLE_H100        6
#define SPINDLE_ONOFF0      7 // typically implemented by driver.c
#define SPINDLE_ONOFF0_DIR  8 // typically implemented by driver.c
#define SPINDLE_ONOFF1      9
#define SPINDLE_ONOFF1_DIR 10
#define SPINDLE_PWM0       11 // typically implemented by driver.c
#define SPINDLE_PWM0_NODIR 12 // typically implemented by driver.c
#define SPINDLE_PWM1       13 // typically implemented by driver.c
#define SPINDLE_PWM1_NODIR 14
#define SPINDLE_PWM2       15
#define SPINDLE_PWM2_NODIR 16
#define SPINDLE_PWM0_CLONE 17
#define SPINDLE_SOLENOID   18
#define SPINDLE_STEPPER    19
#define SPINDLE_NOWFOREVER 20
#define SPINDLE_MY_SPINDLE 30

#define SPINDLE_ALL_VFD ((1<<SPINDLE_HUANYANG1)|(1<<SPINDLE_HUANYANG2)|(1<<SPINDLE_GS20)|(1<<SPINDLE_YL620A)|(1<<SPINDLE_MODVFD)|(1<<SPINDLE_H100)|(1<<SPINDLE_NOWFOREVER))
#define SPINDLE_ALL (SPINDLE_ALL_VFD|(1<<SPINDLE_PWM0))

typedef int8_t spindle_id_t;
typedef int8_t spindle_num_t;

// if changed to > 8 bits planner_cond_t needs to be changed too
typedef union {
    uint8_t value;
    uint8_t mask;
    struct {
        uint8_t on               :1, //!< 主轴是否开启，1表示开启，0表示关闭。
                ccw              :1, //!< 主轴转速是否为逆时针方向，1表示逆时针，0表示顺时针。
                pwm              :1, //!< 主轴的PWM信号是否已反转。此字段用于PWM反转设置，1表示反转，0表示不反转。
                reserved         :1, //!< 保留位，暂时未使用。
                
                                     //!< 假设你有一个 CNC 系统，在运行程序时，如果启用了覆盖功能，
                                     //!< 操作员可以通过一个控制面板或软件，实时调整主轴转速 (S) 或进给速度 (F)。
                                     //!< 这个调整是 临时的，不需要修改 G-code 本身，只是影响当前的运行过程。
                                     //!< 如果系统中 override_disable 被设置为 1，则即使操作员想调整这些参数，
                                     //!< 也无法进行修改，系统将严格按照 G-code 中设定的 S 和 F 值进行工作，不允许任何动态覆盖。
                override_disable :1, //!< 是否禁用覆盖功能。1表示禁用覆盖，0表示启用覆盖。
                encoder_error    :1, //!< 是否存在编码器错误。1表示存在错误，0表示无错误。
                at_speed         :1, //!< 主轴是否已经达到设定转速。1表示已达到设定转速，0表示尚未达到。
                synchronized     :1; //!< 是否已同步，1表示主轴已同步，0表示未同步。
    };
} spindle_state_t;

/*! \brief  Bitmap flags for spindle capabilities. */
typedef union {
    uint16_t value; //!< All bitmap flags.
    struct {
        uint16_t variable          :1, //!< 支持可变主轴转速。
                direction         :1, //!< 主轴方向控制（例如 M4 指令）。
                at_speed          :1, //!< 支持主轴转速反馈（主轴是否已达到设定转速）。
                laser             :1, //!< 主轴可用于控制激光。
                pwm_invert        :1, //!< 支持主轴PWM输出反转（改变PWM信号的极性）。
                pid               :1, //!< 支持PID控制（用于精确的转速控制）。
                pwm_linearization :1, //!< 支持PWM线性化（通过校正控制信号使转速与PWM占空比线性关系）。
                rpm_range_locked  :1, //!< 主轴转速范围（最小值和最大值）不能从设置中继承。
                gpio_controlled   :1, //!< 主轴的开启/关闭和方向通过GPIO控制（硬件控制）。
                cmd_controlled    :1, //!< 主轴的控制通过命令控制，例如通过ModBus协议。
                cloned            :1, //!< 主轴是克隆的（可能是从其他设备复制设置）。
                unassigned        :5; //!< 未分配的保留位。
    };
} spindle_cap_t;

/*! \brief Used when HAL driver supports spindle synchronization. */
typedef struct {
    float rpm;
    float rpm_low_limit;
    float rpm_high_limit;
    float angular_position; //!< Number of revolutions since last reset
    float rpm_programmed;
    uint32_t index_count;
    uint32_t pulse_count;
    uint32_t error_count;
    bool at_speed_enabled;
    spindle_state_t state_programmed;
} spindle_data_t;

typedef enum {
    SpindleData_Counters,           //!< 0
    SpindleData_RPM,                //!< 1
    SpindleData_AngularPosition,    //!< 2
    SpindleData_AtSpeed             //!< 3
} spindle_data_request_t;

typedef enum {
    SpindleType_PWM,        //!< 0 这种类型的主轴通过 脉宽调制（PWM） 信号来控制其转速。PWM 信号的占空比（duty cycle）直接决定了主轴的转速，通常用于 DC 电机或无刷电机。
    SpindleType_Basic,      //!< 1 - on/off + optional direction 这种类型的主轴只有开启/关闭功能，可以选择性地增加方向控制。通常用于 开/关 控制的电机，如某些传统的步进电机或者 DC 电机。
    SpindleType_VFD,        //!< 2 这种类型的主轴控制通常与 变频驱动器（VFD, Variable Frequency Drive） 结合使用。VFD 控制主轴的转速，并且能够通过改变电压和频率来调节交流电机的转速。通常用于工业设备中的高功率电机。
    SpindleType_Solenoid,   //!< 3 这种类型的主轴控制用于 电磁铁，通常用于控制切割工具或激光器等附加设备的启停。它不控制转速，而是控制是否激活工具。
    SpindleType_Stepper,    //!< 4 这种类型的主轴由 步进电机 控制，步进电机能够通过分步控制实现精确的位置和转速控制。步进电机可以控制主轴的精确转速和旋转方向，适用于需要精密定位和速度控制的应用。
    SpindleType_Null,       //!< 5 此类型表示没有主轴控制。
} spindle_type_t;

typedef enum {
    SpindleHAL_Raw,         //!< 0 - 原始状态 (只读状态)
    SpindleHAL_Configured,  //!< 1 - 已配置状态 (已经配置了硬件设置)
    SpindleHAL_Active,      //!< 2 - 活动状态 (主轴处于运行状态)
} spindle_hal_t;

struct spindle_ptrs;    // members defined below
struct spindle_pwm;     // members defined below
struct spindle_param;   // members defined below

/*! \brief Pointer to function for configuring a spindle.
\param spindle a pointer to a \ref spindle_struct.
\returns \a true if successful, \false if not.
*/
typedef bool (*spindle_config_ptr)(struct spindle_ptrs *spindle);

/*! \brief Pointer to function for setting the spindle state.
\param state a \a spindle_state_t union variable.
\param rpm spindle RPM.
*/
typedef void (*spindle_set_state_ptr)(struct spindle_ptrs *spindle, spindle_state_t state, float rpm);

#ifdef GRBL_ESP32
typedef void (*esp32_spindle_off_ptr)(struct spindle_ptrs *spindle);
#endif

/*! \brief Pointer to function for getting the spindle state.
\returns state in a \a spindle_state_t union variable.
*/
typedef spindle_state_t (*spindle_get_state_ptr)(struct spindle_ptrs *spindle);

/*! \brief Pointer to function for converting a RPM value to a PWM value.

Typically this is a wrapper for the spindle_compute_pwm_value() function provided by the core.

\param rpm spindle RPM.
\returns the corresponding PWM value.
*/
typedef uint_fast16_t (*spindle_get_pwm_ptr)(struct spindle_ptrs *spindle, float rpm);

/*! \brief Pointer to function for updating spindle speed on the fly.
\param pwm new spindle PWM value.
\returns the actual PWM value used.

__NOTE:__ this function will be called from an interrupt context.
*/
typedef void (*spindle_update_pwm_ptr)(struct spindle_ptrs *spindle, uint_fast16_t pwm);

/*! \brief Pointer to function for updating spindle RPM.
\param rpm spindle RPM.
*/
typedef void (*spindle_update_rpm_ptr)(struct spindle_ptrs *spindle, float rpm);

/*! \brief Pointer to function for getting spindle data.
\param request request type as a \a #spindle_data_request_t enum.
\returns pointer to the requested information in a spindle_data_t structure.

__NOTE:__ this function requires input from a spindle encoder.
*/
typedef spindle_data_t *(*spindle_get_data_ptr)(spindle_data_request_t request);

/*! \brief Pointer to function for resetting spindle data. */
typedef void (*spindle_reset_data_ptr)(void);

/*! \brief Pointer to function for outputting a spindle on pulse.
Used for Pulses Per Inch (PPI) laser mode.
\param pulse_length spindle on length in microseconds.
*/
typedef void (*spindle_pulse_on_ptr)(uint_fast16_t pulse_length);

typedef struct {
    float rpm;
    float start;
    float end;
} pwm_piece_t;

typedef union {
    uint8_t value;
    uint8_t mask;
    struct {
        uint8_t enable_rpm_controlled :1, // PWM spindle only
                laser_mode_disable    :1, // PWM spindle only
                type                  :5,
                pwm_disable           :1; // PWM spindle only
    };
} spindle_settings_flags_t;

typedef struct {
    float rpm_max;
    float rpm_min;
    float pwm_freq;
    float pwm_period;                           // currently unused
    float pwm_off_value;
    float pwm_min_value;
    float pwm_max_value;
    float at_speed_tolerance;                   //!< Tolerance in percent of programmed speed.
    pwm_piece_t pwm_piece[SPINDLE_NPWM_PIECES];
    pid_values_t pid;
    uint16_t ppr;                               //!< Spindle encoder pulses per revolution (PPR).
    spindle_state_t invert;
    spindle_settings_flags_t flags;
} spindle_settings_t;

typedef struct {
    uint8_t port_on;
    uint8_t port_dir;
    uint8_t port_pwm;
    spindle_settings_t cfg;
} spindle1_settings_t;

typedef union {
    uint8_t value;
    struct {
        uint8_t invert_pwm         :1, //!< NOTE: set (by driver) when inversion is done in code
                always_on          :1,
                cloned             :1,
                laser_mode_disable :1,
                unused             :4;
    };
} spindle_pwm_flags_t;

//!* \brief Precalculated values that may be set/used by HAL driver to speed up RPM to PWM conversions if variable spindle is supported. */
typedef struct spindle_pwm {
    uint32_t f_clock;
    spindle_settings_t *settings;
    uint_fast16_t period;
    uint_fast16_t off_value;    //!< NOTE: this value holds the inverted version if software PWM inversion is enabled by the driver.
    uint_fast16_t min_value;
    uint_fast16_t max_value;
    float rpm_min;              //!< Minimum spindle RPM.
    float pwm_gradient;
    bool invert_pwm;            //!< deprecated, use bit in flags instead
    bool always_on;             //!< deprecated, use bit in flags instead
    bool cloned;                //!< deprecated, use bit in flags instead
    spindle_pwm_flags_t flags;
    int_fast16_t offset;
    uint_fast16_t n_pieces;
    pwm_piece_t piece[SPINDLE_NPWM_PIECES];
    uint_fast16_t (*compute_value)(struct spindle_pwm *pwm_data, float rpm, bool pid_limit);
} spindle_pwm_t;

typedef union
{
    spindle_pwm_t *pwm;
} spindle_context_ptr_t __attribute__ ((__transparent_union__));

/*! \brief Handlers and data for spindle support. */
struct spindle_ptrs {
    spindle_id_t id;                    //!< 主轴ID，在主轴注册时分配。
    uint8_t ref_id;                     //!< 主轴的参考ID，在主轴注册时分配。人为给定
    struct spindle_param *param;        //!< 指向当前主轴参数的指针，在主轴启用时分配。
    spindle_type_t type;                //!< 主轴类型。
    spindle_cap_t cap;                  //!< 主轴的功能标志。
    spindle_context_ptr_t context;      //!< 可选的主轴特定上下文指针，用于保存主轴特有的上下文数据。
    uint_fast16_t pwm_off_value;        //!< PWM信号关闭时的值。
    float rpm_min;                      //!< 最小主轴转速。
    float rpm_max;                      //!< 最大主轴转速。
    float at_speed_tolerance;           //!< 设定转速的容差，单位为百分比。
    spindle_config_ptr config;          //!< 可选的配置主轴的处理程序（回调函数）。
    spindle_set_state_ptr set_state;    //!< 用于设置主轴状态的处理程序（回调函数）。
    spindle_get_state_ptr get_state;    //!< 用于获取主轴状态的处理程序（回调函数）。
    spindle_get_pwm_ptr get_pwm;        //!< 用于根据转速计算主轴PWM值的处理程序（回调函数）。
    spindle_update_pwm_ptr update_pwm;  //!< 用于更新主轴PWM输出的处理程序（回调函数）。
    spindle_update_rpm_ptr update_rpm;  //!< 用于更新主轴转速的处理程序（回调函数）。
#ifdef GRBL_ESP32
    esp32_spindle_off_ptr esp32_off;    //!< 针对ESP32的特定问题（在中断上下文中处理浮点数时的处理程序），用于修复ESP32平台中的雪花问题。
#endif
    // 可选的入口点（处理程序）。
    spindle_pulse_on_ptr pulse_on;      //!< 可选的处理程序，用于脉冲每英寸（PPI）模式。激光PPI插件需要此处理程序。
    spindle_get_data_ptr get_data;      //!< 可选的处理程序，用于获取主轴数据。用于主轴同步操作，来自 `hal.spindle_data.get` 。
    spindle_reset_data_ptr reset_data;  //!< 可选的处理程序，用于重置主轴数据。用于主轴同步操作，来自 `hal.spindle_data.reset`。
};

typedef struct spindle_ptrs spindle_ptrs_t;

//! \brief  Data used for Constant Surface Speed (CSS) mode calculations.
typedef struct {
    float surface_speed;    //!< Surface speed in millimeters/min
    float target_rpm;       //!< Target RPM at end of movement
    float delta_rpm;        //!< Delta between start and target RPM
    float max_rpm;          //!< Maximum spindle RPM
    float tool_offset;      //!< Tool offset
    uint_fast8_t axis;      //!< Linear (tool) axis
} spindle_css_data_t;

/*! \brief Structure used for holding the current state of an enabled spindle. */
typedef struct spindle_param {
    float rpm;
    float rpm_overridden;
    spindle_state_t state;
    override_t override_pct;    //!< Spindle RPM override value in percent
    spindle_css_data_t css;     //!< Data used for Constant Surface Speed Mode (CSS) calculations, NULL if not in CSS mode.
    spindle_ptrs_t *hal;
} spindle_param_t;

typedef struct {
    spindle_get_data_ptr get;      //!< Optional handler for getting spindle data. Required for spindle sync.
    spindle_reset_data_ptr reset;  //!< Optional handler for resetting spindle data. Required for spindle sync.
} spindle_data_ptrs_t;

/*! \brief Structure holding data passed to the callback function called by spindle_enumerate_spindles(). */
typedef struct  {
    spindle_id_t id;
    uint8_t ref_id;
    spindle_num_t num;
    const char *name;
    bool enabled;
    bool is_current;
    const spindle_ptrs_t *hal;
} spindle_info_t;

/*! \brief Pointer to callback function called by spindle_enumerate_spindles().
\param spindle prointer to a \a spindle_info_t struct.
*/
typedef bool (*spindle_enumerate_callback_ptr)(spindle_info_t *spindle, void *data);

void spindle_set_override (spindle_ptrs_t *spindle, override_t speed_override);

// Called by g-code parser when setting spindle state and requires a buffer sync.
bool spindle_sync (spindle_ptrs_t *spindle, spindle_state_t state, float rpm);

// Sets spindle running state with direction, enable, and spindle RPM.
bool spindle_set_state (spindle_ptrs_t *spindle, spindle_state_t state, float rpm);

// Spindle speed calculation and limit handling
float spindle_set_rpm (spindle_ptrs_t *spindle, float rpm, override_t speed_override);

// Restore spindle running state with direction, enable, spindle RPM and appropriate delay.
bool spindle_restore (spindle_ptrs_t *spindle, spindle_state_t state, float rpm);

void spindle_all_off (void);

//
// The following functions are not called by the core, may be called by driver code.
//

#define spindle_validate_at_speed(d, r) { d.rpm = r; d.state_programmed.at_speed = !d.at_speed_enabled || (d.rpm >= d.rpm_low_limit && d.rpm <= d.rpm_high_limit); }
/*
__attribute__((always_inline)) static inline void spindle_validate_at_speed (spindle_data_t *spindle_data, float rpm)
{
    spindle_data->rpm = rpm;
    spindle_data->state_programmed.at_speed = !spindle_data->at_speed_enabled || (spindle_data->rpm >= spindle_data->rpm_low_limit && spindle_data->rpm <= spindle_data->rpm_high_limit);
}
*/

bool spindle_precompute_pwm_values (spindle_ptrs_t *spindle, spindle_pwm_t *pwm_data, spindle_settings_t *settings, uint32_t clock_hz);

spindle_id_t spindle_register (const spindle_ptrs_t *spindle, const char *name);

spindle_id_t spindle_add_null (void);

uint8_t spindle_get_count (void);

bool spindle_select (spindle_id_t spindle_id);

spindle_cap_t spindle_get_caps (bool active);

void spindle_update_caps (spindle_ptrs_t *spindle, spindle_pwm_t *pwm_caps);

void spindle_bind_encoder (const spindle_data_ptrs_t *encoder_data);

bool spindle_set_at_speed_range (spindle_ptrs_t *spindle, spindle_data_t *spindle_data, float rpm);

spindle_ptrs_t *spindle_get_hal (spindle_id_t spindle_id, spindle_hal_t hal);

const char *spindle_get_name (spindle_id_t spindle_id);

spindle_id_t spindle_get_default (void);

spindle_num_t spindle_enable (spindle_id_t spindle_id);

bool spindle_enumerate_spindles (spindle_enumerate_callback_ptr callback, void *data);

//

bool spindle_is_enabled (spindle_num_t spindle_num);

bool spindle_is_on (void);

spindle_ptrs_t *spindle_get (spindle_num_t spindle_num);

#if N_SPINDLE > 1

typedef void (*spindle1_settings_changed_ptr)(spindle1_settings_t *settings);

spindle1_settings_t *spindle1_settings_add (bool claim_ports);
void spindle1_settings_register (spindle_cap_t cap, spindle1_settings_changed_ptr on_changed);

#endif

#endif // _SPINDLE_CONTROL_H_
